#include "mybignum.h"
#include <QDebug>

MyBIGNUM::MyBIGNUM()
{
    m_num=BN_new();
}

MyBIGNUM::MyBIGNUM(BIGNUM *bn){
    m_num=bn;
}

MyBIGNUM::~MyBIGNUM()
{
    if(m_num){
        BN_free(m_num);
    }
}

MyBIGNUM::MyBIGNUM(const MyBIGNUM &other){
    m_num=BN_new();
    BN_copy(m_num,other.m_num);
}

MyBIGNUM::MyBIGNUM(const MyBIGNUM &&other){
    m_num=BN_new();
    BN_copy(m_num,other.m_num);
}

MyBIGNUM& MyBIGNUM::operator=(const MyBIGNUM& other){
    BN_copy(this->m_num,other.m_num);
    return *this;
}
MyBIGNUM& MyBIGNUM::operator=(const MyBIGNUM&& other){
    BN_copy(this->m_num,other.m_num);
    return *this;
}

bool MyBIGNUM::setNumFromWord(BN_ULONG word){
    if(m_num){
        return BN_set_word(m_num,word)==1;
    }
    return false;
}

bool MyBIGNUM::setNumFromDec(std::string dec)
{
    if(m_num){
        BN_free(m_num);
        m_num=nullptr;
    }
    if(BN_dec2bn(&m_num,dec.data())){
        return true;
    }
    return false;
}

bool MyBIGNUM::setNumFromHex(std::string hex)
{
    if(m_num){
        BN_free(m_num);
        m_num=nullptr;
    }
    if(BN_hex2bn(&m_num,hex.data())){
        return true;
    }
    return false;
}

bool MyBIGNUM::setNumFromUChar(const unsigned char *s, int len)
{
    return BN_bin2bn(s,len,m_num)==m_num;
}

bool MyBIGNUM::setBitsToOne(int bits)
{
    bool ret=true;
    for(int i=0;i<bits;++i){
        ret&=(BN_set_bit(m_num,i)==1);
    }
    return ret;
}

void MyBIGNUM::setNumToZero()
{
    BN_zero(m_num);
}
//加法
MyBIGNUM MyBIGNUM::operator+(const MyBIGNUM& a){
    BIGNUM *tem=BN_new();
    if(BN_add(tem,a.m_num,m_num)==1)
        return MyBIGNUM(tem);
    return nullptr;
}

MyBIGNUM MyBIGNUM::addMod(const MyBIGNUM &a, const MyBIGNUM &mod)
{
    BIGNUM* tem=BN_new();
    BN_mod_add_quick(tem,m_num,a.m_num,mod.m_num);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator+(const BN_ULONG& a){
    BIGNUM *tem=BN_new();
    BN_add_word(tem,a);
    BN_add(tem,tem,m_num);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator+(BN_ULONG&& a){
    BIGNUM *tem=BN_new();
    BN_add_word(tem,a);
    BN_add(tem,tem,m_num);
    return MyBIGNUM(tem);
}

MyBIGNUM operator+(BN_ULONG a,const MyBIGNUM& b){
    BIGNUM *tema=BN_new();
    BN_set_word(tema,a);
    BN_add(tema,tema,b.m_num);
    return MyBIGNUM(tema);
}

//模加
MyBIGNUM MyBIGNUM::addMod(BN_ULONG a,const MyBIGNUM& mod){
    BIGNUM* tem=BN_new();
    if(BN_set_word(tem,a)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    if(BN_mod_add_quick(tem,m_num,tem,mod.m_num)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::addMod(BN_ULONG a, const MyBIGNUM &&mod)
{
    BIGNUM* tem=BN_new();
    if(BN_set_word(tem,a)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    if(BN_mod_add_quick(tem,m_num,tem,mod.m_num)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::addMod(const MyBIGNUM& a,BN_ULONG mod){
    BIGNUM* tem=BN_new();
    if(BN_set_word(tem,mod)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    if(BN_mod_add_quick(tem,m_num,a.m_num,tem)==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    return MyBIGNUM(tem);
}
//减法
MyBIGNUM MyBIGNUM::operator-(const MyBIGNUM& a){
    BIGNUM *tem=BN_new();
    BN_sub(tem,m_num,a.m_num);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator-(const BN_ULONG& a){
    BIGNUM *tem=BN_new();
    auto ret=BN_copy(tem,m_num);
    if(!ret){
        return MyBIGNUM(tem);
    }
    BN_sub_word(tem,a);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator-(BN_ULONG&& a){
    BIGNUM *tem=BN_new();
    auto ret=BN_copy(tem,m_num);
    if(!ret){
        return MyBIGNUM(tem);
    }
    BN_sub_word(tem,a);
    return MyBIGNUM(tem);
}

MyBIGNUM operator-(BN_ULONG a,const MyBIGNUM& b){
    BIGNUM *tema=BN_new();
    BN_set_word(tema,a);
    BN_sub(tema,tema,b.m_num);
    return MyBIGNUM(tema);
}
//模减
MyBIGNUM MyBIGNUM::subMod(const MyBIGNUM &a, const MyBIGNUM &mod)
{
    BIGNUM *tem=BN_new();
    BN_mod_sub_quick(tem,m_num,a.m_num,mod.m_num);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::subMod(BN_ULONG a,const MyBIGNUM& mod){
    BIGNUM *tema=BN_new();
    if(BN_set_word(tema,a)==0){
        BN_free(tema);
        return MyBIGNUM(nullptr);
    }
    if(BN_mod_sub_quick(tema,m_num,tema,mod.m_num)==0){
        BN_free(tema);
        return MyBIGNUM(nullptr);
    }
    return MyBIGNUM(tema);
}

MyBIGNUM MyBIGNUM::subMod(const MyBIGNUM& a,BN_ULONG mod){
    BIGNUM *temM=BN_new();
    if(BN_set_word(temM,mod)==0){
        BN_free(temM);
        return MyBIGNUM(nullptr);
    }
    if(BN_mod_sub_quick(temM,m_num,a.m_num,temM)==0){
        BN_free(temM);
        return MyBIGNUM(nullptr);
    }
    return MyBIGNUM(temM);
}
//成法
MyBIGNUM MyBIGNUM::operator*(const MyBIGNUM& a){
    BIGNUM *tem=BN_new();
    if(BN_mul(tem,a.m_num,m_num,nullptr)==0){
        BN_free(tem);
        return nullptr;
    }
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator*(const BN_ULONG& a){
    BIGNUM *tem=BN_new();
    BN_set_word(tem,a);
    BN_CTX* ctx=BN_CTX_new();
    BN_mul(tem,tem,m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
MyBIGNUM MyBIGNUM::operator*(BN_ULONG&& a){
    BIGNUM *tem=BN_new();
    BN_set_word(tem,a);
    BN_CTX* ctx=BN_CTX_new();
    BN_mul(tem,tem,m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
MyBIGNUM operator*(BN_ULONG a,const MyBIGNUM& b){
    BIGNUM *tema=BN_new();
    BN_set_word(tema,a);
    BN_CTX* ctx=BN_CTX_new();
    BN_mul(tema,tema,b.m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tema);
}

MyBIGNUM MyBIGNUM::mulMod(const MyBIGNUM &a, const MyBIGNUM &mod)
{
    BIGNUM *tem=BN_new();
    BN_CTX* ctx=BN_CTX_new();
    if(BN_mod_mul(tem,m_num,a.m_num,mod.m_num,ctx)==0){
        BN_free(tem);
        BN_CTX_free(ctx);
        return nullptr;
    }
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
MyBIGNUM MyBIGNUM::mulMod(BN_ULONG a, const MyBIGNUM &mod)
{
    BIGNUM *tem=BN_new();
    int ret=BN_set_word(tem,a);
    if(ret==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    BN_CTX* ctx=BN_CTX_new();
    if(BN_mod_mul(tem,m_num,tem,mod.m_num,ctx)==0){
        BN_free(tem);
        BN_CTX_free(ctx);
        return MyBIGNUM(nullptr);
    }
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
MyBIGNUM MyBIGNUM::mulMod(const MyBIGNUM &a, BN_ULONG mod)
{
    BIGNUM *tem=BN_new();
    int ret=BN_set_word(tem,mod);
    if(ret==0){
        BN_free(tem);
        return MyBIGNUM(nullptr);
    }
    BN_CTX* ctx=BN_CTX_new();
    if(BN_mod_mul(tem,m_num,a.m_num,tem,ctx)==0){
        BN_free(tem);
        BN_CTX_free(ctx);
        return MyBIGNUM(nullptr);
    }
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
//除法
MyBIGNUM MyBIGNUM::inverseMod(const MyBIGNUM &mod)
{
    BIGNUM *tem=BN_new();
    if(BN_mod_inverse(tem,m_num,mod.m_num,nullptr)==tem){
        return MyBIGNUM(tem);
    }
    BN_free(tem);
    return MyBIGNUM(nullptr);
}

MyBIGNUM MyBIGNUM::operator/(const MyBIGNUM& a){
    BIGNUM *tem=BN_new();
    BN_CTX *ctx=BN_CTX_new();
    BN_div(tem,nullptr,m_num,a.m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::operator/(const BN_ULONG& a){
    BIGNUM *tem=BN_new();
    BN_copy(tem,m_num);
    BN_div_word(tem,a);
    return MyBIGNUM(tem);
}
MyBIGNUM MyBIGNUM::operator/(BN_ULONG&& a){
    BIGNUM *tem=BN_new();
    BN_copy(tem,m_num);
    BN_div_word(tem,a);
    return MyBIGNUM(tem);
}
MyBIGNUM operator/(BN_ULONG a,const MyBIGNUM& b){
    BIGNUM *tema=BN_new();
    BN_set_word(tema,a);
    auto ctx=BN_CTX_new();
    BN_div(tema,nullptr,tema,b.m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tema);
}
//取模
MyBIGNUM MyBIGNUM::operator%(const MyBIGNUM& mod){
    BIGNUM *tem=BN_new();
    auto ctx=BN_CTX_new();
    BN_mod(tem,m_num,mod.m_num,ctx);
    BN_CTX_free(ctx);
    return MyBIGNUM(tem);
}
//异或运算
MyBIGNUM MyBIGNUM::operator^(const MyBIGNUM &a)
{
    BIGNUM *tem=BN_new();
    int bytes=std::ceil((double)std::max(BN_num_bits(a.m_num),BN_num_bits(m_num))/8);
    unsigned char* bin1=new unsigned char[bytes];
    memset(bin1,0,bytes);
    auto ret=BN_bn2bin(a.m_num,bin1);
    unsigned char* bin2=new unsigned char[bytes];
    memset(bin2,0,bytes);
    auto ret1=BN_bn2bin(m_num,bin2);
    if(ret&&ret1){
        for(int i=0;i<bytes;++i){
            bin1[i]=bin1[i]^bin2[i];
        }
    }
    BN_bin2bn(bin1,bytes,tem);
    delete[] bin1;
    delete[] bin2;
    return MyBIGNUM(tem);
}

MyBIGNUM MyBIGNUM::exp(const MyBIGNUM &a)
{
    BIGNUM *r=BN_new();
    BN_CTX *ctx=BN_CTX_new();
    BN_exp(r,m_num,a.m_num,nullptr);
    BN_CTX_free(ctx);
    return r;
}

MyBIGNUM MyBIGNUM::exp(BN_ULONG a)
{
    BIGNUM *r=BN_new();
    BIGNUM *b=BN_new();
    BN_set_word(b,a);
    BN_CTX *ctx=BN_CTX_new();
    BN_exp(r,m_num,b,ctx);
    BN_CTX_free(ctx);
    return r;
}

int MyBIGNUM::cmp(const MyBIGNUM &a)
{
    return BN_cmp(m_num,a.m_num);
}

bool MyBIGNUM::operator==(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret==0;
}
bool MyBIGNUM::operator!=(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret!=0;
}
bool MyBIGNUM::operator<=(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret==0||ret==-1;
}
bool MyBIGNUM::operator>=(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret==0||ret==1;
}
bool MyBIGNUM::operator<(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret==-1;
}
bool MyBIGNUM::operator>(const MyBIGNUM &a)
{
    int ret=BN_cmp(m_num,a.m_num);
    return ret==1;
}
//打印
void MyBIGNUM::print(){
    auto s=BN_bn2dec(m_num);
    qDebug()<<s;
    OPENSSL_free(s);
}

std::string MyBIGNUM::toString()
{
    std::shared_ptr<char> buf{BN_bn2dec(m_num),
                              [](char* buf){
                                  if(buf){OPENSSL_free(buf);}
                              }
                            };
    return std::string(buf.get());
}

BN_ULONG MyBIGNUM::toBN_ULONG()
{
    return BN_get_word(m_num);
}

std::ostream& operator<<(std::ostream& os, const MyBIGNUM& dt) {
    auto s = BN_bn2dec(dt.m_num);
    os << s;
    OPENSSL_free(s);
    return os;
}

MyBIGNUM MyBIGNUM::getANewPramix(int bits,int safe){
    BIGNUM* bn=BN_new();
    int ret=BN_generate_prime_ex(bn,bits,safe,nullptr,nullptr,nullptr);
    if(ret==1){
        return MyBIGNUM(bn);
    }
    return MyBIGNUM();
}

MyBIGNUM MyBIGNUM::rand(int bits){
    BIGNUM* rand = BN_new();
    BN_rand(rand,bits,0,0);
    return MyBIGNUM(rand);
}

MyBIGNUM MyBIGNUM::rand(const MyBIGNUM& limit)
{
    BIGNUM* rand = BN_new();
    BN_set_word(rand,-1);
    BN_rand_range(rand,limit.m_num);
    return MyBIGNUM(rand);
}
